#include <cstdlib>
#include <ctime>
#include <cerrno>
#include <attr/xattr.h>
#include <cmath>
#include <limits.h>
#include <signal.h>

#include "tagfs.h"
#include "tools.h"

// --------------------------------------------------------------------
bool TagFS::init(FILE* fsFile)
// --------------------------------------------------------------------
{
	fs = fsFile;
	if (fs == NULL)
	{
		return false;
	}

	fseeko(fs, 0, SEEK_SET);

	char tmp[6];
	uint8_t read;
	read = fread(tmp, 1, 6, fs);
	if (read != 6)
	{
		return false;
	}

	std::string header;
	header.assign(tmp, 4);
	if (header != "TgFS")
	{
		return false;
	}

	uint16_t nodeSize = *(uint16_t*)(&tmp[4]);
	fseek(fs, 0, SEEK_END);
	uint64_t total = ftello(fs);

	fsNodes.init(fs, 6, nodeSize, ((total-6) / nodeSize));
	fsMaps.init(&fsNodes);

	fsMaps.print();

	fifopass = 0;
	
	return true;
}

// ---------------------
void TagFS::destroy()
// ---------------------
{
	fsMaps.writeMaps();
	fclose(fs);
}

// -------------------
bool TagFS::fifowait()
// -------------------
{
	struct timespec req;
	req.tv_sec = 0;
	req.tv_nsec = 10000000; // 0.01 sec
	if (nanosleep(&req, NULL) == -1)
	{
		return false;
	}
	return true;
}

// --------------------------
bool TagFS::readdir(std::string path, std::list<std::string>* dirlist, std::list<struct stat>* statlist)
// --------------------------
{
	// FIFO
	++fifopass;
	uint16_t mypass = fifopass;
	fifo.push(mypass);
	while (fifo.front() != mypass && fifo.size() > 1 && fifo.size() != -1)
	{
		// printf("!!! [readdir] waiting for %u\n", mypass);
		if (!fifowait()) return -EINTR;
	}

	std::list<std::string> list;
	std::list<std::string>::iterator li;
	tools::splitPath(&list, path+"/");

	bool ok = false;
	for (li=list.begin(); li!=list.end(); ++li)
	{
		if ((*li) == "!$")
		{
			ok = true;
			++li;
			if (li != list.end())
			{
				if (!fifo.empty()) fifo.pop();
				return false;
			}
			break;
		}
	}

	if (!ok)
	{
		if (!fifo.empty()) fifo.pop();
		return false;
	}

	// create and process query tree to get list of matching FIDs
	list.pop_back();
	TagFS_TagTree tree;
	if (!tree.parse(list))
	{
		if (!fifo.empty()) fifo.pop();
		return false;
	}

	std::list<uint64_t> fidlist = tree.compute(&fsMaps);
	std::list<uint64_t>::iterator il;
	TagFile file;
	struct stat stbuf;
	
	// iterate through fids
	for (il=fidlist.begin(); il!=fidlist.end(); ++il)
	{
		file = fsMaps.getFile((*il));
		if (file.node != 0)
		{
			stbuf.st_mode = S_IFREG | 0664;
			stbuf.st_nlink = 0;
			stbuf.st_size = file.size;
			stbuf.st_uid = getuid();
			stbuf.st_gid = getgid();
			stbuf.st_ctime = file.ctime;
			stbuf.st_atime = file.atime;
			stbuf.st_mtime = file.mtime;
			stbuf.st_blksize = fsNodes.getSize();
			stbuf.st_blocks = ( (file.size % fsNodes.getSize()) == 0 ? (file.size / fsNodes.getSize()) : (file.size / fsNodes.getSize()+1) );
			statlist->push_back( stbuf );
			dirlist->push_back( file.name );
		}
	}
	if (!fifo.empty()) fifo.pop();
	return true;
}

// -----------------------------------------------------
int TagFS::fgetattr(std::string path, struct stat* stbuf, struct fuse_file_info* info)
// -----------------------------------------------------
{
	// FIFO
	++fifopass;
	uint16_t mypass = fifopass;
	fifo.push(mypass);
	while (fifo.front() != mypass && fifo.size() > 1 && fifo.size() != -1)
	{
		// printf("!!! [fgetattr] waiting for %u\n", mypass);
		if (!fifowait()) return -EINTR;
	}

	std::list<std::string> list;
	tools::splitPath(&list, path+"/");
	// check for /!<tag>/<file>
	if (list.size() == 2 && list.front()[0] == '!' && list.front()[1] != '$')
	{
		return sptag__getattr(list.front().substr(1), list.back(), stbuf, info);
	}

	if (info != NULL)
	{
		if (info->fh != 0)
		{
			uint64_t fid = info->fh;
			bool ok = false;
			for (std::list<uint64_t>::iterator i=openedFiles.begin(); i!=openedFiles.end(); ++i)
			{
				if (*i == fid)
				{
					ok = true;
					if (!fifo.empty()) fifo.pop();
					break;
				}
			}
			if (!ok)
			{
				if (!fifo.empty()) fifo.pop();
				return -EBADF;
			}
		}
		else
		{
			if (!fifo.empty()) fifo.pop();
			return -EBADF;
		}
	}

	std::list<std::string>::iterator li;
	bool is_file = false;
	for (li=list.begin(); li!=list.end(); ++li)
	{
		if ((*li) == "!$")
		{
			++li;
			if (li == list.end())
			{
				is_file = false;
			}
			else
			{
				is_file = true;
			}
			break;
		}
	}

	if (list.size() == 2 && list.front()[0] == '!' && list.front()[1] != '$')
	{
		is_file = true;
		--li;
	}

	if (list.size() == 1 && list.front().substr(0, 6)  == ".Trash")
	{
		if (!fifo.empty()) fifo.pop();
		return -ENOENT;
	}

	if (!is_file)
	{
		// directory
		stbuf->st_mode = S_IFDIR | 0775;
		stbuf->st_uid = getuid();
		stbuf->st_gid = getgid();
		stbuf->st_size = 0;
		stbuf->st_ctime = stbuf->st_atime = stbuf->st_mtime = 0;
		stbuf->st_blksize = fsNodes.getSize();
		stbuf->st_blocks = 0;
		if (!fifo.empty()) fifo.pop();
		return 0;
	}
	
	TagFile file = fsMaps.getFile(*li);
	if (file.node != 0)
	{
		stbuf->st_mode = S_IFREG | 0664;
		stbuf->st_uid = getuid();
		stbuf->st_gid = getgid();
		stbuf->st_nlink = 0;
		stbuf->st_size = file.size;
		stbuf->st_ctime = file.ctime;
		stbuf->st_atime = file.atime;
		stbuf->st_mtime = file.mtime;
		stbuf->st_blksize = fsNodes.getSize();
		stbuf->st_blocks = ( (file.size % fsNodes.getSize()) == 0 ? (file.size / fsNodes.getSize()) : (file.size / fsNodes.getSize()+1) );
	}
	else
	{
		if (!fifo.empty()) fifo.pop();
		return -ENOENT;
	}
	if (!fifo.empty()) fifo.pop();
	return 0;
}

// ---------------------------------
int TagFS::open(std::string path, struct fuse_file_info* info)
// ---------------------------------
{
	// FIFO
	++fifopass;
	uint16_t mypass = fifopass;
	fifo.push(mypass);
	while (fifo.front() != mypass && fifo.size() > 1 && fifo.size() != -1)
	{
		// printf("!!! [open] waiting for %u\n", mypass);
		if (!fifowait()) return -EINTR;
	}

	// /!<tags>/<file> check
	std::list<std::string> list;
	tools::splitPath(&list, path+"/");
	if (list.size() == 2 && list.front()[0] == '!' && list.front()[1] != '$')
	{
		return sptag__open(list.front().substr(1), list.back(), info);
	}

	std::string name = path.substr(path.find_last_of('/')+1);
	TagFile file = fsMaps.getFile(name);
	
	uint64_t fid;

	if (file.node == 0 && !(info->flags & O_CREAT ))
	{
		if (!fifo.empty()) fifo.pop();
		return -ENOENT;
	}
	if (file.node != 0 && (info->flags & (O_CREAT | O_EXCL)) )
	{
		if (!fifo.empty()) fifo.pop();
		return -EEXIST;
	}
	
	if (file.node == 0 && !(info->flags & O_RDONLY) && (info->flags & O_CREAT))
	{
		// no file, create a file
		file.name = name;
		file.size = 0;
		file.ctime = file.mtime = file.atime = time(0);
		file.node = fsNodes.reserve();
		if (file.node == 0)
		{
			if (!fifo.empty()) fifo.pop();
			return -ENOSPC;
		}
		fsMaps.addFile(file);
		fsMaps.addTag("", file.name);
	}
	else if (file.node != 0 && !(info->flags & O_RDONLY) && (info->flags & O_TRUNC))
	{
		file.ctime = file.mtime = file.atime = time(0);
		// truncate
		ftruncate(path, 0, info);
	}
	file.atime = time(0);
	info->fh = fid = fsMaps.getFID(file.name);
	fsMaps.updateFile(fid, file);
	openedFiles.push_back(fid);
	// printf("[open] opened %llu with starting node %llu\n", fid, file.node);
	if (!fifo.empty()) fifo.pop();
	return 0;
}

// ---------------------------------------------------------------------------------
int TagFS::ftruncate(std::string path, uint64_t length, struct fuse_file_info* info)
// ---------------------------------------------------------------------------------
{
	// FIFO
	++fifopass;
	uint16_t mypass = fifopass;
	fifo.push(mypass);
	while (fifo.front() != mypass && fifo.size() > 1 && fifo.size() != -1)
	{
		// printf("!!! [ftruncate] waiting for %u\n", mypass);
		if (!fifowait()) return -EINTR;
	}

	// /!<tags>/<file> check
	std::list<std::string> list;
	tools::splitPath(&list, path+"/");
	if (list.size() == 2 && list.front()[0] == '!' && list.front()[1] != '$')
	{
		return sptag__truncate(list.front().substr(1), list.back(), length, info);
	}

	uint64_t fid = 0;
	if (info != NULL)
	{
		if (info->fh != 0)
		{
			fid = info->fh;
			bool ok = false;
			for (std::list<uint64_t>::iterator i=openedFiles.begin(); i!=openedFiles.end(); ++i)
			{
				if (*i == fid)
				{
					ok = true;
					break;
				}
			}
			if (!ok)
			{
				if (!fifo.empty()) fifo.pop();
				return -EBADF;
			}
		}
		else
		{
			if (!fifo.empty()) fifo.pop();
			return -EBADF;
		}
	}

	std::string filename = path.substr(path.find_last_of('/')+1);
	TagFile file = fsMaps.getFile(filename);
	
	if (file.node == 0)
	{
		if (!fifo.empty()) fifo.pop();
		return -ENOENT;
	}

	if (fid == 0)
	{
		fid = fsMaps.getFID(filename);
	}
	
	if (length > file.size)
	{
		// append 0s
		fuse_file_info tmpinfo;
		tmpinfo.flags = O_WRONLY;
		tmpinfo.fh = fid;
		write("internal call", 0, length-file.size, file.size, &tmpinfo);
	}
	else if (length < file.size)
	{
		// reduce
		std::list<uint64_t> nodes = fsNodes.getList(file.node);
		uint64_t tofree = (file.size - length ) / fsNodes.getSize();
		for (std::list<uint64_t>::reverse_iterator ri=nodes.rbegin(); ri!=nodes.rend(); ++ri)
		{
			if (tofree == 0)
			{
				char buf[fsNodes.getSize()];
				fsNodes.read(*ri, buf);
				fsNodes.write(*ri, buf, false);
				break;
			}
			fsNodes.free(*ri);
			--tofree;
		}
	}
	file.size = length;
	file.mtime = file.ctime = time(0);
	fsMaps.updateFile(fid, file);
	
	if (!fifo.empty()) fifo.pop();
	return 0;
}

// ---------------------------------------------------------------------------------------------------------
int TagFS::read(std::string path, char* outbuf, uint64_t size, uint64_t offset, struct fuse_file_info* info)
// ---------------------------------------------------------------------------------------------------------
{
	// FIFO
	++fifopass;
	uint16_t mypass = fifopass;
	fifo.push(mypass);
	while (fifo.front() != mypass && fifo.size() > 1 && fifo.size() != -1)
	{
		// printf("!!! [read] waiting for %u, is %u, total %d\n", mypass, fifo.front(), fifo.size());
		usleep(100);
	}

	// check for /!<tags>/<file>
	std::list<std::string> list;
	tools::splitPath(&list, path+"/");
	if (list.size() == 2 && list.front()[0] == '!' && list.front()[1] != '$')
	{
		return sptag__read(list.front().substr(1), list.back(), outbuf, size, offset, info);
	}

	uint64_t fid = 0;
	if (info->fh != 0)
	{
		fid = info->fh;
		bool ok = false;
		for (std::list<uint64_t>::iterator i=openedFiles.begin(); i!=openedFiles.end(); ++i)
		{
			if (*i == fid)
			{
				ok = true;
				break;
			}
		}
		if (!ok)
		{
			if (!fifo.empty()) fifo.pop();
			return -EBADF;
		}
	}
	else
	{
		if (!fifo.empty()) fifo.pop();
		return -EBADF;
	}

	TagFile file = fsMaps.getFile(fid);

	if (offset > file.size)
	{
		if (!fifo.empty()) fifo.pop();
		return -ESPIPE;
	}

	char buffer[fsNodes.getSize()];
	std::pair<uint64_t, int> res;
	uint64_t nodeoff = 0;

	// set offset
	std::list<uint64_t> nodes = fsNodes.getList(file.node);
	std::list<uint64_t>::iterator i;

	for (i=nodes.begin(); i!=nodes.end(); ++i)
	{
		if (nodeoff <= offset)
		{
			nodeoff += fsNodes.getSize();
		}
		else
		{
			break;
		}
	}
	
	uint16_t toread = nodeoff - offset;
	// printf("!!! toread = %u\n", toread);

	uint64_t total = 0;
	--i;
	res.first = *i;

	while (i!=nodes.end() && total < size)
	{
		res = fsNodes.read(res.first, buffer);
		if (res.second != 0)
		{
			if (!fifo.empty()) fifo.pop();
			return res.second;
		}
		for (uint16_t k=fsNodes.getSize()-toread; k<fsNodes.getSize(); ++k)
		{
			outbuf[total] = buffer[k];
			++total;
			if (total >= size)
			{
				break;
			}
		}
		++i;
		toread = fsNodes.getSize();
	}

	if (!fifo.empty()) fifo.pop();
	return total;
}

// --------------------------------------------------------------
int TagFS::release(std::string path, struct fuse_file_info* info)
// --------------------------------------------------------------
{
	// FIFO
	++fifopass;
	uint16_t mypass = fifopass;
	fifo.push(mypass);
	while (fifo.front() != mypass && fifo.size() > 1 && fifo.size() != -1)
	{
		// printf("!!! [release] waiting for %u\n", mypass);
		if (!fifowait()) return -EINTR;
	}

	openedFiles.remove(info->fh);
	if (!fifo.empty()) fifo.pop();
	return 0;
}

// -----------------------------------------------------------------------------------------------------------------
int TagFS::write(std::string path, const char* data, uint64_t size, uint64_t offset, struct fuse_file_info* info)
// -----------------------------------------------------------------------------------------------------------------
{
	// FIFO
	++fifopass;
	uint16_t mypass = fifopass;
	fifo.push(mypass);
	while (fifo.front() != mypass && fifo.size() > 1 && fifo.size() != -1)
	{
		// printf("!!! [write] waiting for %u\n", mypass);
		if (!fifowait()) return -EINTR;
	}

	// check for /!<tags>/<file>
	std::list<std::string> list;
	tools::splitPath(&list, path+"/");
	if (list.size() == 2 && list.front()[0] == '!' && list.front()[1] != '$')
	{
		return sptag__write(list.front().substr(1), list.back(), data, size, offset, info);
	}

	uint64_t fid = 0;
	if (path == "internal call")
	{
		fid = info->fh;
	}
	else
	{
		if (info->fh != 0)
		{
			fid = info->fh;
			bool ok = false;
			for (std::list<uint64_t>::iterator i=openedFiles.begin(); i!=openedFiles.end(); ++i)
			{
				if (*i == fid)
				{
					ok = true;
					break;
				}
			}
			if (!ok)
			{
				if (!fifo.empty()) fifo.pop();
				return -EBADF;
			}
		}
		else
		{
			if (!fifo.empty()) fifo.pop();
			return -EBADF;
		}
	}

	TagFile file = fsMaps.getFile(fid);

	// checks
	if (info->flags & O_RDONLY)
	{
		if (!fifo.empty()) fifo.pop();
		return -EACCES;
	}
	if (offset > file.size)
	{
		if (!fifo.empty()) fifo.pop();
		printf("err a\n");
		return -ESPIPE;
	}

	uint64_t databuf = 0;
	uint64_t dataoff = 0;
	char buffer[fsNodes.getSize()];
	uint16_t read;
	uint64_t nextNode;
	uint64_t total = 0;
	std::pair<uint64_t, int> res;


	// set offset
	std::list<uint64_t> nodes = fsNodes.getList(file.node);
	std::list<uint64_t>::iterator i;
	for (i=nodes.begin(); i!=nodes.end(); ++i)
	{
		if (dataoff <= offset)
		{
			dataoff += fsNodes.getSize();
		}
		else
		{
			break;
		}
	}
	
	uint64_t overwrite;
	// overwrite nodes
	if (dataoff <= offset)
	{
		overwrite = 0;
	}
	else
	{
		overwrite = dataoff - offset;
	}
	// printf("!!!! overwrite = %llu (%llu %llu)\n", overwrite, dataoff, offset);

	--i;
	if (overwrite > 0)
	{
		for (i; i!=nodes.end(); ++i)
		{
			res = fsNodes.read(*i, buffer);
			if (res.second != 0)
			{
				if (!fifo.empty()) fifo.pop();
				return res.second;
			}
			uint16_t towrite = ((overwrite % fsNodes.getSize() == 0) ? fsNodes.getSize() : (overwrite % fsNodes.getSize()));
			for (uint64_t k=fsNodes.getSize()-towrite; k<fsNodes.getSize(); ++k)
			{
				if (data == 0)
				{
					buffer[k] = 0;
				}
				else
				{
					buffer[k] = data[databuf];					
				}
				++databuf;
				++total;
				if (total >= size)
				{
					overwrite = 0;
					break;
				}
			}
			if (overwrite <= fsNodes.getSize() && total <= size)
			{
				// printf("!!!! overwriting node %llu\n", *i);
				res = fsNodes.write(*i, buffer, true);
				// printf("!!!! next app node %llu\n", res.first);
				overwrite = 0;
			}
			else
			{
				// printf("!!!! overwriting node %llu\n", *i);
				res.second = fsNodes.write(*i, buffer);
				overwrite -= databuf;
			}
			if (res.second != 0)
			{
				if (!fifo.empty()) fifo.pop();
				return res.second;
			}
			if (overwrite == 0)
			{
				break;
			}
		}
	}
	else if (overwrite == 0)
	{
		res = fsNodes.read(*i, buffer);
		if (res.second != 0)
		{
			if (!fifo.empty()) fifo.pop();
			return res.second;
		}
		if (size > 0)
		{
			res = fsNodes.write(*i, buffer, true);
		}
	}
	
	if (total <= size)
	{
		size = size - total;
	}
	else
	{
		size = 0;
	}

	// add new nodes
	while (size > 0)
	{
		for (uint64_t k=0; k<fsNodes.getSize(); ++k)
		{
			if (data == 0)
			{
				buffer[k] = 0;
			}
			else
			{
				buffer[k] = data[databuf];					
			}
			++databuf;
		}
		if (size > fsNodes.getSize())
		{
			res = fsNodes.write(res.first, buffer, true);
			if (res.second != 0)
			{
				if (!fifo.empty()) fifo.pop();
				return res.second;
			}
			size -= fsNodes.getSize();
			total += fsNodes.getSize();
		}
		else
		{
			res = fsNodes.write(res.first, buffer, false);
			if (res.second != 0)
			{
				if (!fifo.empty()) fifo.pop();
				return res.second;
			}
			total += size;
			size = 0;
		}
	}
	file.size = offset + total;
	file.mtime = time(0);
	fsMaps.updateFile(fid, file);
	if (!fifo.empty()) fifo.pop();
	return total;
}

// -------------------------------------
int TagFS::unlink(std::string path)
// -------------------------------------
{
	// FIFO
	++fifopass;
	uint16_t mypass = fifopass;
	fifo.push(mypass);
	while (fifo.front() != mypass && fifo.size() > 1 && fifo.size() != -1)
	{
		// printf("!!! [unlink] waiting for %u\n", mypass);
		if (!fifowait()) return -EINTR;
	}

	// check for /!<tags>/<file>
	std::list<std::string> list;
	tools::splitPath(&list, path+"/");
	if (list.size() == 2 && list.front()[0] == '!' && list.front()[1] != '$')
	{
		return sptag__unlink(list.front().substr(1), list.back());
	}
	std::string name = path.substr(path.find_last_of('/')+1);

	TagFile file = fsMaps.getFile(name);

	if (file.node == 0)
	{
		if (!fifo.empty()) fifo.pop();
		return -ENOENT;
	}

	// delete
	std::list<uint64_t> nodes = fsNodes.getList(file.node);
	fsNodes.free(nodes);
	fsMaps.rmvFile(file.name);
	if (!fifo.empty()) fifo.pop();
	return 0;
}

// ---------------------------------
int TagFS::utimens(std::string path, const timespec tv[2])
// ---------------------------------
{
	// FIFO
	++fifopass;
	uint16_t mypass = fifopass;
	fifo.push(mypass);
	while (fifo.front() != mypass && fifo.size() > 1 && fifo.size() != -1)
	{
		// printf("!!! [utimens] waiting for %u\n", mypass);
		if (!fifowait()) return -EINTR;
	}

	// check for /!<tags>/<file>
	std::list<std::string> list;
	tools::splitPath(&list, path+"/");
	if (list.size() == 2 && list.front()[0] == '!' && list.front()[1] != '$')
	{
		return sptag__utimens(list.front().substr(1), list.back(), tv);
	}
	
	std::string name = path.substr(path.find_last_of('/')+1);
	TagFile file = fsMaps.getFile(name);

	if (file.node == 0)
	{
		if (!fifo.empty()) fifo.pop();
		return -ENOENT;
	}
	
	file.atime = tv[0].tv_sec;
	file.mtime = tv[1].tv_sec;
	fsMaps.updateFile(file.name, file);
	if (!fifo.empty()) fifo.pop();
	return 0;
}

// -------------------------------------
int TagFS::rename(std::string oldpath, std::string newpath)
// -------------------------------------
{
	// FIFO
	++fifopass;
	uint16_t mypass = fifopass;
	fifo.push(mypass);
	while (fifo.front() != mypass && fifo.size() > 1 && fifo.size() != -1)
	{
		// printf("!!! [rename] waiting for %u\n", mypass);
		if (!fifowait())
		{
			return -EINTR;
		}
	}

	// check for /!<tags>/<file>
	std::list<std::string> list;
	tools::splitPath(&list, oldpath+"/");
	if (list.size() == 2 && list.front()[0] == '!' && list.front()[1] != '$')
	{
		if (!fifo.empty()) fifo.pop();
		return -EPERM;
	}

	// get old name
	std::string oldname = oldpath.substr(oldpath.find_last_of('/')+1);
	TagFile ofile = fsMaps.getFile(oldname);
	if (ofile.node == 0)
	{
		if (!fifo.empty()) fifo.pop();
		return -ENOENT;
	}

	// get new name
	std::string newname = newpath.substr(newpath.find_last_of('/')+1);
	TagFile nfile = fsMaps.getFile(newname);
	if (nfile.node != 0 && oldname != newname)
	{
		if (!fifo.empty()) fifo.pop();
		return -EEXIST;
	}

	// rename
	nfile = ofile;
	nfile.name = newname;
	
	// get tags lists
	std::list<std::string> oldtags, newtags;
	tools::splitPath(&oldtags, oldpath.substr(0, oldpath.find_last_of('/')+1));
	oldtags.unique();
	tools::splitPath(&newtags, newpath.substr(0, newpath.find_last_of('/')+1));
	newtags.unique();

	// remove and add
	std::list<std::string>::iterator oti, nti;
	for (oti=oldtags.begin(); oti!=oldtags.end(); ++oti)
	{
		if ((*oti)[0] != '!')
		{
			fsMaps.rmvTag(*oti, ofile.name);
		}
	}

	for (nti=newtags.begin(); nti!=newtags.end(); ++nti)
	{
		if ((*nti)[0] != '!')
		{
			fsMaps.addTag(*nti, ofile.name);
		}
	}

	nfile.ctime = time(0);
	fsMaps.updateFile(oldname, nfile);
	if (!fifo.empty()) fifo.pop();
	return 0;
}

// ------------------------------------------
int TagFS::access(std::string path, int mode)
// ------------------------------------------
{
	// FIFO
	++fifopass;
	uint16_t mypass = fifopass;
	fifo.push(mypass);
	while (fifo.front() != mypass && fifo.size() > 1 && fifo.size() != -1)
	{
		// printf("!!! [access] waiting for %u\n", mypass);
		if (!fifowait()) return -EINTR;
	}

	// check for /!<tags>/<file>
	std::list<std::string> list;
	tools::splitPath(&list, path+"/");
	if (list.size() == 2 && list.front()[0] == '!' && list.front()[1] != '$')
	{
		return sptag__access(list.front().substr(1), list.back(), mode);
	}

	std::string name = path.substr(path.find_last_of('/')+1);
	TagFile file = fsMaps.getFile(name);

	if (file.node == 0)
	{
		if (!fifo.empty()) fifo.pop();
		return -ENOENT;
	}
	
	file.atime = time(0);
	fsMaps.updateFile(file.name, file);
	if (!fifo.empty()) fifo.pop();
	return 0;
}

// ----------------------------------------------------
int TagFS::statfs(std::string path, struct statvfs* vfs)
// ----------------------------------------------------
{
	// FIFO
	++fifopass;
	uint16_t mypass = fifopass;
	fifo.push(mypass);
	while (fifo.front() != mypass && fifo.size() > 1 && fifo.size() != -1)
	{
		// printf("!!! [statfs] waiting for %u\n", mypass);
		if (!fifowait()) return -EINTR;
	}

	vfs->f_bsize = fsNodes.getSize();
	vfs->f_frsize = fsNodes.getSize()+8; // ignored
	vfs->f_blocks = fsNodes.getAll();
	vfs->f_bfree = fsNodes.getAll() - fsNodes.getUsed();
	vfs->f_bavail = fsNodes.getAll() - fsNodes.getUsed(); // ignored
	vfs->f_files = fsMaps.getFileCount();
	vfs->f_ffree = ULONG_MAX;
	vfs->f_favail = ULONG_MAX;
	vfs->f_fsid = 0; // ignored
	vfs->f_flag = ST_NOSUID; // ignored
	vfs->f_namemax = ULONG_MAX;
//	vfs->f_pathmax = ULONG_MAX;
//	vfs->f_objlinkmax = 0;
//	vfs->f_dirlinkmax = 0;
//	vfs->f_basetype = "SimpleTagFS";
	if (!fifo.empty()) fifo.pop();
	return 0;
}

// ------------------------------------------------------------------------------
int TagFS::getxattr(std::string path, std::string name, char* value, size_t size)
// ------------------------------------------------------------------------------
{
	// FIFO
	++fifopass;
	uint16_t mypass = fifopass;
	fifo.push(mypass);
	while (fifo.front() != mypass && fifo.size() > 1 && fifo.size() != -1)
	{
		// printf("!!! [getxattr] waiting for %u\n", mypass);
		if (!fifowait()) return -EINTR;
	}

	// check for /!<tags>/<file>
	std::list<std::string> list;
	tools::splitPath(&list, path+"/");
	if (list.size() == 2 && list.front()[0] == '!' && list.front()[1] != '$')
	{
		if (!fifo.empty()) fifo.pop();
		return -ENOTSUP;
	}

	if (name != "user.tags")
	{
		if (!fifo.empty()) fifo.pop();
		return -ENOATTR;
	}

	path = path.substr(path.find_last_of('/')+1);
	TagFile file = fsMaps.getFile(path);

	if (file.node == 0)
	{
		if (!fifo.empty()) fifo.pop();
		return -ENOENT;
	}

	std::list<std::string>::iterator i;
	list = fsMaps.getTags(file.name);
	std::string tagString;
	for (i=list.begin(); i!=list.end(); ++i)
	{
		tagString += *i + "/";
	}
	if (!tagString.empty())
	{
		tagString = tagString.substr(0, tagString.size()-1);
	}

	if (size == 0)
	{
		if (!fifo.empty()) fifo.pop();
		return tagString.size();
	}
	else if (size < tagString.size())
	{
		if (!fifo.empty()) fifo.pop();
		return -ERANGE;
	}

	for (size_t z=0; z<size; ++z)
	{
		value[z] = tagString[z];
	}
	
	file.atime = time(0);
	fsMaps.updateFile(file.name, file);
	if (!fifo.empty()) fifo.pop();
	return tagString.size();
}

// ------------------------------------------------------------
int TagFS::listxattr(std::string path, char* resp, size_t size)
// ------------------------------------------------------------
{
	// FIFO
	++fifopass;
	uint16_t mypass = fifopass;
	fifo.push(mypass);
	while (fifo.front() != mypass && fifo.size() > 1 && fifo.size() != -1)
	{
		// printf("!!! [listxattr] waiting for %u\n", mypass);
		if (!fifowait()) return -EINTR;
	}

	// check for /!<tags>/<file>
	std::list<std::string> list;
	tools::splitPath(&list, path+"/");
	if (list.size() == 2 && list.front()[0] == '!' && list.front()[1] != '$')
	{
		if (!fifo.empty()) fifo.pop();
		return -ENOTSUP;
	}

	std::string name = path.substr(path.find_last_of('/')+1);
	TagFile file = fsMaps.getFile(name);

	if (file.node == 0)
	{
		if (!fifo.empty()) fifo.pop();
		return -ENOENT;
	}

	const std::string xattr = "user.tags";
	
	if (size == 0)
	{
		if (!fifo.empty()) fifo.pop();
		return xattr.size();
	}
	else if (size < xattr.size())
	{
		if (!fifo.empty()) fifo.pop();
		return -ERANGE;
	}
	
	size_t i;
	for (i=0; i<xattr.size(); ++i)
	{
		resp[i] = xattr[i];
	}
	resp[i] = 0;

	file.atime = time(0);
	fsMaps.updateFile(file.name, file);
	if (!fifo.empty()) fifo.pop();
	return xattr.size()+1;
}

// -----------------------------------------------------------------------------------------------
int TagFS::setxattr(std::string path, std::string name, std::string value, size_t size, int flags)
// -----------------------------------------------------------------------------------------------
{
	// FIFO
	++fifopass;
	uint16_t mypass = fifopass;
	fifo.push(mypass);
	while (fifo.front() != mypass && fifo.size() > 1 && fifo.size() != -1)
	{
		// printf("!!! [setxattr] waiting for %u\n", mypass);
		if (!fifowait()) return -EINTR;
	}

	// check for /!<tags>/<file>
	std::list<std::string> list;
	tools::splitPath(&list, path+"/");
	if (list.size() == 2 && list.front()[0] == '!' && list.front()[1] != '$')
	{
		if (!fifo.empty()) fifo.pop();
		return -ENOTSUP;
	}
	
	if (name != "user.tags")
	{
		if (!fifo.empty()) fifo.pop();
		return -ENOATTR;
	}

	path = path.substr(path.find_last_of('/')+1);
	TagFile file = fsMaps.getFile(path);

	if (file.node == 0)
	{
		if (!fifo.empty()) fifo.pop();
		return -ENOENT;
	}

	list = fsMaps.getTags(file.name);

	if (!list.empty() && (flags & XATTR_CREATE))
	{
		if (!fifo.empty()) fifo.pop();
		return -EEXIST;
	}
	else if (list.empty() && (flags & XATTR_REPLACE))
	{
		if (!fifo.empty()) fifo.pop();
		return -ENOATTR;
	}
	
	// delete old
	std::list<std::string>::iterator i;
	for (i=list.begin(); i!=list.end(); ++i)
	{
		fsMaps.rmvTag(*i, file.name);
	}
	
	if (size > 0)
	{
		// parse new
		list.clear();
		tools::splitPath(&list, "/"+value+"/");
		for (i=list.begin(); i!=list.end(); ++i)
		{
			fsMaps.addTag(*i, file.name);
		}
	}

	file.ctime = time(0);
	fsMaps.updateFile(file.name, file);
	if (!fifo.empty()) fifo.pop();
	return 0;
}

// -------------------------------------------------------
int TagFS::removexattr(std::string path, std::string name)
// -------------------------------------------------------
{
	// FIFO
	++fifopass;
	uint16_t mypass = fifopass;
	fifo.push(mypass);
	while (fifo.front() != mypass && fifo.size() > 1 && fifo.size() != -1)
	{
		// printf("!!! [removexattr] waiting for %u\n", mypass);
		if (!fifowait()) return -EINTR;
	}

	// check for /!<tags>/<file>
	std::list<std::string> list;
	tools::splitPath(&list, path+"/");
	if (list.size() == 2 && list.front()[0] == '!' && list.front()[1] != '$')
	{
		if (!fifo.empty()) fifo.pop();
		return -ENOTSUP;
	}

	if (name != "user.tags")
	{
		if (!fifo.empty()) fifo.pop();
		return -ENOATTR;
	}

	path = path.substr(path.find_last_of('/')+1);
	TagFile file = fsMaps.getFile(path);

	if (file.node == 0)
	{
		if (!fifo.empty()) fifo.pop();
		return -ENOENT;
	}
	
	list = fsMaps.getTags(file.name);
	
	// delete
	std::list<std::string>::iterator i;
	for (i=list.begin(); i!=list.end(); ++i)
	{
		fsMaps.rmvTag(*i, file.name);
	}

	file.ctime = time(0);
	fsMaps.updateFile(file.name, file);

	if (!fifo.empty()) fifo.pop();
	return 0;
}

